//floading neighbours manager

package it.unibo.deis.lia.mmhc.sfm;

//un server aperto per ogni connessione
//effettua il floading per il numero di hop stabilito per i messaggi di search
//effettua invece il routing mirato dei messaggi di attivazione di servizio e di servizio trovato

import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.BindException;

import java.net.InetAddress;

import java.net.ServerSocket;
import java.net.Socket;

import java.util.Vector;

import it.unibo.deis.lia.mmhc.fcm.Connection;
import it.unibo.deis.lia.mmhc.fcm.ConnectionManager;
import it.unibo.deis.lia.mmhc.spm.LocalService;
import it.unibo.deis.lia.mmhc.spm.Service;
import it.unibo.deis.lia.mmhc.spm.ServiceClient;
import it.unibo.deis.lia.mmhc.spm.ServiceManager;
import it.unibo.deis.lia.mmhc.spm.ServicePathServer;
import it.unibo.deis.lia.mmhc.spm.ServiceServer;

import it.unibo.deis.lia.mmhc.util.Channel;

public class ServerSearchFlooding extends Thread {

	// private Channel channel;
	private String localIP;
	private FindNeighbours finderNeighbours;
	// private boolean isConnectorRole;
	private boolean go = true;
	private ConnectionManager connectionManager;

	public ServerSearchFlooding(Channel channel, FindNeighbours findNeighbours) {
		this.finderNeighbours = findNeighbours;
		// this.channel=channel;
		this.localIP = channel.getChannelLocalIPAddress();
		try {
			connectionManager=ConnectionManager.getInstance();
		} catch (Exception e) {
			System.err.println("Impossibile creare il connectionManager");
			e.printStackTrace();
		}
	}

	public ServerSearchFlooding(String localIP, FindNeighbours findNeighbours) {
		this.finderNeighbours = findNeighbours;
		// this.channel=channel;
		this.localIP = localIP;
		try {
			connectionManager=ConnectionManager.getInstance();
		} catch (Exception e) {
			System.err.println("Impossibile creare il connectionManager");
			e.printStackTrace();
		}
	}

	public void run() {
		// // UDP...
		//
		// byte[] r_data;
		// DatagramSocket socket = null;
		// DatagramPacket r_packet = null;
		// ByteArrayInputStream b_in = null;
		//
		// try {
		// r_data = new byte[10000];
		// b_in = new ByteArrayInputStream(r_data);
		// r_packet = new DatagramPacket(r_data, r_data.length);
		// socket = new DatagramSocket(2401, InetAddress.getByName(localIP));
		// } catch (Exception e) {
		// System.err
		// .println("Impossibile aprire datagramsoket su " + localIP);
		// e.printStackTrace();
		// }
		//
		// while (go) {
		// try {
		// socket.receive(r_packet);
		// b_in.reset();
		// ObjectInputStream o_in = new ObjectInputStream(b_in);
		// Object obj = o_in.readObject();
		// byte[] ack = "ack".getBytes();
		// String senderAdd = r_packet.getAddress().getHostAddress();
		// DatagramPacket s_paket = new DatagramPacket(ack, ack.length,
		// r_packet.getAddress(), r_packet.getPort());
		// socket.send(s_paket);
		// if (obj instanceof SearchObject) {
		// SearchObject sobj = (SearchObject) obj;
		// searchFlooding(sobj, senderAdd, socket);
		//
		// }
		// if (obj instanceof ServiceGoBack){
		// ServiceGoBack gbobj=(ServiceGoBack)obj;
		// forewordService(gbobj,senderAdd,socket);
		// }
		// } catch (Exception e) {
		// e.printStackTrace();
		// }
		// }

		// TCP...varie problematiche inutile aprire connessioni tcp per un
		// pacchetto

		try {
			ServerSocket ss = null;
			try {
				ss = new ServerSocket(2401, 0, InetAddress.getByName(localIP));
				//connectionManager=ConnectionManager.getInstance();
				System.err.println("\tCreato flooding server sull'indirizzo:"
						+ localIP);

			} catch (BindException e) {
				go = false;
			}

			while (go) {// go verifica se la connessione è già stata attivata o
				// se si verificano degli errori!

				System.err.println("\tServerFloodingManager accept on "
						+ localIP);
				Socket input = ss.accept();
				

				String remoteSenderIP = input.getInetAddress().getHostAddress();

				if (connectionManager.addConnection(new Connection(
						remoteSenderIP, localIP)))
					System.err
							.println("Aggiunta in tcm una nuova connessione con indirizzo remoto "
									+ remoteSenderIP);

				System.err
						.println("\tServerFloodingNeighbours connessione stabilita con "
								+ remoteSenderIP);

				ObjectInputStream ois = new ObjectInputStream(input
						.getInputStream());
				// System.err.println("\tRicevuto oggetto da " + remoteSenderIP
				// + " alle " + System.currentTimeMillis());
				Object obj = ois.readObject();
				Connection con = connectionManager.getConnection(
						remoteSenderIP, localIP);
				if (obj instanceof SearchService) {
					searchService(obj, remoteSenderIP, con);

				} else if (obj instanceof FindedService) {
					findedService(obj, con);

				} else if (obj instanceof ServiceAttivation) {
					serviceAttivation(obj, con);

				}

				else
					System.err.println("Ricevuto oggetto sconosciuto");

			}
		}

		catch (Exception e) {
			System.err.println("Errore servizio vicini su indirizzo " + localIP
					+ " " + e);
			e.printStackTrace();
		}
	}

	private void serviceAttivation(Object obj, Connection con) throws Exception {
		ServiceAttivation attivation = (ServiceAttivation) obj;
		ServiceManager serviceManager = ServiceManager.getInstance();
		serviceManager.addServicePathServer(localIP);
		if (!attivation.isServiceOpened()) {
			con.numberHopIncrease(attivation.getRichiedente());// Quasi
																// impossibile
																// che il
																// richiedente
																// non sia nella
																// lista
			con.increaseServiceUsed(attivation.getRichiedente());
			if (attivation.getNumberNextGoHop() >= attivation.getGoPath()
					.size()) {
				// attivare il servizio sulla porta richiesta non so
				// se il servizio sia fuori o dentro al middelware
				// direi dentro...
				System.err
						.println("La richiesta di attovazione è arrivata al capolinea");
				ServiceServer serviceServer = new ServiceServer(attivation
						.getGoPath(), attivation.getBackPath(), attivation
						.getServizio().getPort(), attivation.getFornitore(),
						attivation.getRichiedente(), true, attivation
								.getServizio().getName());
				serviceServer.start();
				ServicePathServer.addServerService(serviceServer);
				attivation.setServiceOpened(true);
				System.err.println("Lanciato il server di servizio");
				String nextBackHop = attivation.getNextBackHop();
				send(attivation, nextBackHop, 2401);

			} else {
				String nextGoHop = attivation.getNextGoHop();
				send(attivation, nextGoHop, 2401);
			}

		} else if (attivation.isServiceOpened()) {
			con.numberHopIncrease(attivation.getFornitore());// Quasi
																// impossibile
																// che il
																// richiedente
																// non sia nella
																// lista
			con.increaseServiceOfferd(attivation.getFornitore());
			if (attivation.getNumberNextBackHop() >= attivation.getBackPath()
					.size()) {
				System.err
						.println("attivazione avvenjuta con successo apro il client");

				ServiceClient serviceClient = new ServiceClient(attivation
						.getServizio().getName(), attivation.getGoPath(),
						attivation.getBackPath(), attivation.getServizio()
								.getPort(), attivation.getFornitore(),
						attivation.getRichiedente());
				serviceClient.start();
			} else {
				String nextBackHop = attivation.getNextBackHop();
				send(attivation, nextBackHop, 2401);
			}
		}

	}

	private void findedService(Object obj, Connection con) throws Exception {
		FindedService serviceFinded = (FindedService) obj;
		// System.err.println("qui ci arrivo");
		// service.addGoHop(localIP);
		con
				.numberHopIncrease(((Service) serviceFinded.getOgg())
						.getFornitore());// incremento numero hop della
											// connessione se nuovo
		con.findedServiceNumberIncrease((Service) serviceFinded.getOgg());
		if (serviceFinded.getHopBackNumber() == serviceFinded
				.getHopBackSequence().size())
			ClientServiceSearch.serviceTrovato(serviceFinded);
		else {
			String nextHop = serviceFinded.nextBackHop();
			System.err.println("Invio risposta verso " + nextHop
					+ " per il servizio "
					+ ((Service) serviceFinded.getOgg()).getName()
					+ " fornito da "
					+ ((Service) serviceFinded.getOgg()).getFornitore());
			send(serviceFinded, nextHop, 2401);

		}
	}

	private void searchService(Object obj, String remoteSenderIP, Connection con)
			throws Exception {

		SearchService searchMessage = (SearchService) obj;
		if(con==null)
			System.err.println("con non null");
		con.numberHopIncrease(searchMessage.getRichiedente());// incremento
																// numero hop
																// della
																// connessione
																// se nuovo
		con.searchServiceRequestNumberIncrease();// incremento il numero di
													// richieste effettuate
		if (searchMessage.getHopToLive() == 0) {
			// qui semplicemente lo stampa a video dipende dal tipo
			// di
			// messaggio...
			searchMessage.addGoHop(localIP);
			searchMessage.addBackHop(remoteSenderIP);
			System.err.println(searchMessage.getSearchGoPath().elementAt(0));
			System.err.println(searchMessage.getSearchBackPath().elementAt(0));
			execute(searchMessage);
		} else {
			int hopToLive = searchMessage.getHopToLive();
			if (hopToLive > 0) {

				hopToLive--;
				searchMessage.setHopToLive(hopToLive);
				searchMessage.addGoHop(localIP);

				searchMessage.addBackHop(remoteSenderIP);

				execute(searchMessage);
				Vector<String> neighbours = neighbours(finderNeighbours
						.getNeighbourgs(), remoteSenderIP);
				for (int i = 0; i < neighbours.size(); i++) {
					send(searchMessage, neighbours.elementAt(i), 2401);
				}
			}
		}

	}

	// rimuove da tutti i vicini il mittente
	private Vector<String> neighbours(Vector<String> allNeighbours,
			String sender) {
		Vector<String> neigbours = new Vector<String>();
		for (int i = 0; i < allNeighbours.size(); i++) {
			if (!allNeighbours.elementAt(i).equalsIgnoreCase(sender))
				neigbours.add(allNeighbours.elementAt(i));
		}

		return neigbours;
	}

	private void send(Object obj, String ip, int port) throws Exception {
		int attempts = 5;
		Socket out = null;
		do {
			try {
				out = new Socket(ip, port);
			} catch (BindException e) {
			} catch (Exception e) {
				Thread.sleep(1500);
				attempts--;
				if (attempts == 0)
					throw e;
				System.err
						.println("Impossibile aprire o trovare socket di risposta "
								+ e);
			}
		} while (attempts > 0 && out == null);
		ObjectOutputStream oos = new ObjectOutputStream(out.getOutputStream());
		oos.flush();
		oos.writeObject(obj);
		oos.flush();
		out.close();
	}

	private Vector<String> invertVector(Vector<String> vector) {
		Vector<String> back = new Vector<String>();
		for (int i = vector.size() - 1; i > -1; i--) {
			back.add(vector.elementAt(i));

		}
		return back;
	}

	// stampa solamente l'oggetto contenuto ma in base hai servizi si potrebbe
	// fare qualcosa di meglio basta aggiungere
	// funzionalità tipo lanciare il servicePathManager per creare un percorso
	// di andata e ritorno
	private void execute(Object o) {
		try {
			System.err.println("Messaggio di ricerca per il servizio: "
					+ (String) ((SearchService) o).getOgg() + " inviato da "
					+ (String) ((SearchService) o).getRichiedente());
			boolean trovato = false;// se il servizio desiderato si gererà
			// l'apposito messaggio
			Vector<Service> serviziLocali = LocalService.getInstance()
					.getServizi();
			Service servizio = null;
			for (int i = 0; serviziLocali.size() > i; i++) {
				if (serviziLocali.elementAt(i).getName().equalsIgnoreCase(
						(String) ((SearchService) o).getOgg())) {
					trovato = true;
					servizio = serviziLocali.elementAt(i);
					break;
				}
			}
			if (trovato) {
				System.err.println("Trovato servizio " + servizio.getName());
				FindedService service = new FindedService(-1, servizio,
						((SearchService) o).getSearchGoPath(),
						((SearchService) o).getSearchBackPath());
				System.err.println("Creato pacchetto con trovato go"
						+ service.getHopGoSequence().elementAt(0) + "rit"
						+ service.getHopBackSequence().elementAt(0));
				int attempts = 5;
				Socket out = null;
				String nextHop = service.nextBackHop();
				System.err.println("Provo ad aprire a rispondere a " + nextHop);
				do {
					try {
						out = new Socket(nextHop, 2401);
					} catch (BindException e) {
					} catch (Exception e) {
						Thread.sleep(1500);
						attempts--;
						if (attempts == 0)
							throw e;
						System.err
								.println("Impossibile trovare socket di risposta "
										+ e);
					}
				} while (attempts > 0 && out == null);
				ObjectOutputStream oos = new ObjectOutputStream(out
						.getOutputStream());
				oos.flush();
				oos.writeObject(service);
				// System.err.println("Inviata risposta trovato servizio");
				oos.flush();
				out.close();
			}
		} catch (Exception e) {
			System.err
					.println("Qualche errore nell'invio della risposta di un oggetto trovato "
							+ e);
		}

	}
	// private void forewordService(FindedService gbobj,String senderAddr,
	// DatagramSocket socket) {
	// gbobj.addBackHop(senderAddr);
	// if(gbobj.getHopGoNumber()==gbobj.getHopGoSequence().size()){
	// ClientServiceSearch.serviceTrovato();
	// }else{
	// String nextBackHop=gbobj.nextGoHop();
	// try {
	// socket.setSoTimeout(3000);
	// ByteArrayOutputStream fos = new ByteArrayOutputStream();
	// ObjectOutputStream oos = new ObjectOutputStream(fos);
	// oos.writeObject(gbobj);
	// oos.close();
	//
	// // end serialization
	// byte[] buff = new byte[1024];
	// byte[] sendByte = fos.toByteArray();
	//				
	// int t = 3;
	// boolean ackricevuto = false;
	// while (t > 0 && !ackricevuto) {
	// try {
	// DatagramPacket packet = new DatagramPacket(sendByte,
	// sendByte.length, InetAddress.getByName(nextBackHop), 2401);
	// socket.send(packet);
	// packet = new DatagramPacket(buff, buff.length);
	//					
	//
	// socket.receive(packet);
	// String received = new String(packet.getData(), 0,
	// packet.getLength());
	// if (received.equalsIgnoreCase("ack"))
	// ackricevuto = true;
	//
	// } catch (SocketTimeoutException e) {
	// t--;
	// }
	// }
	//				
	// socket.setSoTimeout(0);
	//
	// } catch (Exception e) {
	// System.err.println("Impossibile inviare il fatto di aver trovato il servizio");
	// e.printStackTrace();
	// }
	// }
	//			
	//			
	//		
	// }

	// tutto udp...tralasciato con connessioni permanenti che non vengono mai
	// chiuse fino a quando è attivo il vicino

	// private void searchFlooding(SearchService sobj, String senderAdd,
	// DatagramSocket socket) {
	// String servizioCercato = (String) sobj.getOgg();
	// sobj.addHop(senderAdd);
	// LocalService service = LocalService.getInstance();
	// Vector<String> servizi = service.getServizi();
	// // Cerca se tra i servizi offerti c'è quello richiesto nel caso in cui
	// // ci sia lo rimanda al richidente tramite il metodo trovato
	// for (int i = 0; i < servizi.size(); i++) {
	// if (servizioCercato.equalsIgnoreCase(servizi.elementAt(i)))
	// trovato(sobj,socket);
	// }
	// if (sobj.getHopToLive() > 0) {
	// System.err.println("Arrivato pacchetto di ricerca del servizio: "+(String)sobj.getOgg());
	// sobj.setHopToLive(sobj.getHopToLive() - 1);
	// Vector<String> neighbours = neighbours(finderNeighbours
	// .getNeighbourgs(), senderAdd);
	// try {
	// socket.setSoTimeout(3000);
	// ByteArrayOutputStream fos = new ByteArrayOutputStream();
	// ObjectOutputStream oos = new ObjectOutputStream(fos);
	// oos.writeObject(sobj);
	// oos.close();
	//
	// // end serialization
	// byte[] buff = new byte[1024];
	// byte[] sendByte = fos.toByteArray();
	// for (int i = 0; i < neighbours.size(); i++) {
	// int t = 3;
	// boolean ackricevuto = false;
	// while (t > 0 && !ackricevuto) {
	// try {
	// DatagramPacket packet = new DatagramPacket(sendByte,
	// sendByte.length, InetAddress.getByName(neighbours
	// .elementAt(i)), 2401);
	// socket.send(packet);
	// packet = new DatagramPacket(buff, buff.length);
	//					
	//
	// socket.receive(packet);
	// String received = new String(packet.getData(), 0,
	// packet.getLength());
	// if (received.equalsIgnoreCase("ack"))
	// ackricevuto = true;
	//
	// } catch (SocketTimeoutException e) {
	// t--;
	// }
	// }
	// }
	// socket.setSoTimeout(0);
	//
	// } catch (Exception e) {
	// System.err.println("Impossibile fare il flooding");
	// e.printStackTrace();
	// }
	//	
	// } else if (sobj.getHopToLive() == 0)
	// System.err.println("Arrivato oggetto di search al caplinea che cercava il servizio: "+(String)sobj.getOgg());
	//
	// }
	//
	// private void trovato(SearchService sobj,DatagramSocket socket) {
	// FindedService backmsg=new
	// FindedService(0,"Ho questo servizio con queste caratteristiche",invertVector(sobj.getSearchPath()));
	// String nextBackHop=backmsg.nextGoHop();
	// try {
	// socket.setSoTimeout(3000);
	// ByteArrayOutputStream fos = new ByteArrayOutputStream();
	// ObjectOutputStream oos = new ObjectOutputStream(fos);
	// oos.writeObject(backmsg);
	// oos.close();
	//
	// // end serialization
	// byte[] buff = new byte[1024];
	// byte[] sendByte = fos.toByteArray();
	//			
	// int t = 3;
	// boolean ackricevuto = false;
	// while (t > 0 && !ackricevuto) {
	// try {
	// DatagramPacket packet = new DatagramPacket(sendByte,
	// sendByte.length, InetAddress.getByName(nextBackHop), 2401);
	// socket.send(packet);
	// packet = new DatagramPacket(buff, buff.length);
	//				
	//
	// socket.receive(packet);
	// String received = new String(packet.getData(), 0,
	// packet.getLength());
	// if (received.equalsIgnoreCase("ack"))
	// ackricevuto = true;
	//
	// } catch (SocketTimeoutException e) {
	// t--;
	// }
	// }
	//			
	// socket.setSoTimeout(0);
	//
	// } catch (Exception e) {
	// System.err.println("Impossibile inviare il fatto di aver trovato il servizio");
	// e.printStackTrace();
	// }
	// }
}
